package contegor.scire.structure.plugins;

import static contegor.scire.structure.Constants.ABSTRACT;
import static contegor.scire.structure.Constants.ACTION;
import static contegor.scire.structure.Constants.CONCRETE;
import static contegor.scire.structure.Constants.FIRST;
import static contegor.scire.structure.Constants.LANG_ACTION;
import static contegor.scire.structure.Constants.LANG_BELONGING_OBJECT;
import static contegor.scire.structure.Constants.LANG_COMPOSITION_OBJECT;
import static contegor.scire.structure.Constants.LANG_ENTITY;
import static contegor.scire.structure.Constants.LANG_MEANING_GROUP;
import static contegor.scire.structure.Constants.LANG_NEXUS;
import static contegor.scire.structure.Constants.LANG_TIME_OBJECT;
import static contegor.scire.structure.Constants.LAST_RESULT;
import static contegor.scire.structure.Constants.MAIN;
import static contegor.scire.structure.Constants.TO_BE_COMPOUND;
import static contegor.scire.structure.Constants.TO_BE_SER;
import static contegor.scire.structure.Constants.TO_DATE;
import static contegor.scire.structure.Constants.TO_HAVE;
import static contegor.scire.structure.Constants.UNDEFINED;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.ExecutionException;

import contegor.scire.core.Action;
import contegor.scire.core.Attribute;
import contegor.scire.core.Concept;
import contegor.scire.core.Id;
import contegor.scire.core.IdFragment;
import contegor.scire.core.Link;
import contegor.scire.core.Link.Flow;
import contegor.scire.core.Primitive;
import contegor.scire.core.modifications.ChainCreateLink;
import contegor.scire.core.modifications.CreateConcept;
import contegor.scire.core.queries.Filter;
import contegor.scire.core.queries.GetConcepts;
import contegor.scire.core.queries.GetConceptsExtensible;
import contegor.scire.core.queries.GetLinksFromSource;
import contegor.scire.core.queries.IsConnectedWith;
import contegor.scire.core.queries.Query;
import contegor.scire.structure.Constants;
import contegor.scire.structure.Context;
import contegor.scire.structure.lang.MeaningGroupData;
import contegor.scire.structure.lang.UnderstandException;
import contegor.scire.structure.queries.GetAbstractionsOf;
import contegor.scire.structure.queries.GetInstancesOf;

/**
 * Core plug-in for core Scire {@link Action}s.
 * All 'action' methods should be public, static, return void and with {@link Context} as first parameter
 * and {@link Action} as second.
 */
public class Core {

//  public static void link(Context ctx, Action action) {
//    Id   origin    = action.getLink(Constants.ORIGIN).getLink(Constants.PARAMETER).getLinks().values().iterator().next().getTarget();
//    Id   destiny   = action.getLink(Constants.DESTINY).getLink(Constants.PARAMETER).getLinks().values().iterator().next().getTarget();
//    Id   companion = action.getLink(Constants.COMPANION).getLink(Constants.PARAMETER).getLinks().values().iterator().next().getTarget();
//    Flow flow      = Flow.valueOf(companion.getLastIdFragment().toString());
//    ctx.process(new CreateLink(flow, origin.getParentId(), destiny, origin.getLastIdFragment()));
//  }

  /**
   * Takes the {@link Constants#LANG_DESTINY_OBJECT}, {@link Constants#LANG_DIRECT_OBJECT} and {@link Constants#RELATION} from a
   * generic {@link Action}, and creates the specified relation between the {@link Constants#RECEPTOR} and 
   * the {@link Constants#TARGET}.
   */
  public static void relate(Context ctx, Action action) throws InterruptedException, ExecutionException {
    Link receptor = action.getLink(Constants.LANG_DESTINY_OBJECT).getLink(Constants.PARAMETER);
    Link target   = action.getLink(Constants.LANG_DIRECT_OBJECT).getLink(Constants.PARAMETER);
    Link relation = action.getLink(Constants.RELATION).getLink(Constants.PARAMETER);

    Map<Id,List<IdFragment>> groupedReceptors = new TreeMap<Id, List<IdFragment>>();
    for (Link link: receptor.getLinks().values()) {
      Id receptorId = link.getTarget();
      List<IdFragment> group = groupedReceptors.get(receptorId.getParentId());
      if (group == null) {
        group = new ArrayList<IdFragment>();
        groupedReceptors.put(receptorId.getParentId(), group);
      }
      group.add(receptorId.getLastIdFragment());
    }

    for (Map.Entry<Id, List<IdFragment>> entry: groupedReceptors.entrySet()) {
      GetConceptsExtensible<Link> ctask = new GetConceptsExtensible<Link>(entry.getKey(), entry.getValue().toArray(new IdFragment[entry.getValue().size()]));
      for (Link link: target.getLinks().values())
        ctask.withIt(replicateLinks(relation, replicateLinks(link, new ChainCreateLink(Flow.DOWNSTREAM, link.getTarget()))));
      ctx.process(ctask).get();
    }
  }
  
  private static ChainCreateLink replicateLinks(Link src, ChainCreateLink dst) {
    for (Link link: src.getLinks().values())
      dst.withIt(replicateLinks(link, new ChainCreateLink(link.getFlow(), link.getTarget())));
    return dst;
  }

  /**
   * Creates temporal concept <i>user.last result</i> and creates {@link Link}s from it to
   * {@link Constants#LANG_DIRECT_OBJECT} {@link Concept}s. 
   */
  public static void returnDirectObject(Context ctx, Action action) throws InterruptedException, ExecutionException {
    Link target = action.getLink(Constants.LANG_DIRECT_OBJECT).getLink(Constants.PARAMETER);
    CreateConcept createLastResult = ctx.newCreateTransientConcept(LAST_RESULT);
    for (Link link: target.getLinks().values())
      createLastResult.withIt(ctx.newCreateTransientLink(Flow.DOWNSTREAM, link.getTarget()));
    ctx.process(createLastResult).get();
  }

  /** Adds a temp. link from groupLink to MAIN concept. */
  public static void addMainToGroupLink(Context ctx, Action action, Primitive currentGroup, MeaningGroupData currentData, Link wordLink, Link groupLink) throws InterruptedException, ExecutionException, UnderstandException {
    ctx.process(ctx.newCreateTransientLink(Flow.DOWNSTREAM, currentGroup.getId().getParentId(), MAIN, currentGroup.getId().getLastIdFragment(), groupLink.getTarget())).get();
    // The article, nexus & co. have passed, and currently we have the main element, so it's the best place for this call.
    setGroupLangEntity(ctx, action, currentGroup, currentData, wordLink, groupLink);
  }

  /** Adds a temp. link from groupLink to FIRST concept. */
  public static void addFirstToGroupLink(Context ctx, Action action, Primitive currentGroup, MeaningGroupData currentData, Link wordLink, Link groupLink) throws InterruptedException, ExecutionException, UnderstandException {
    ctx.process(ctx.newCreateTransientLink(Flow.DOWNSTREAM, currentGroup.getId().getParentId(), FIRST, currentGroup.getId().getLastIdFragment(), groupLink.getTarget())).get();
  }

  /**
   * Sets {@link MeaningGroupData}'s {@link Query} with current word {@link Concept}, been this an 'entity' and the target of 'groupLink'.
   * Also sets the group as UNDEFINED if the given 'entity' is 'abstract'.
   */
  public static void setGroupQueryWithEntity(Context ctx, Action action, Primitive currentGroup, MeaningGroupData currentData, Link wordLink, Link groupLink) throws InterruptedException, ExecutionException, UnderstandException {
    // Concept concept = ctx.process(new GetConcepts(groupLink.getTarget())).get().get(0);
    Link conceptIs = ctx.process(new Filter<Link>(new IsConnectedWith<Link>(TO_BE_SER), new GetLinksFromSource(groupLink.getTarget()))).get().get(0);
    if (conceptIs.isLinkedWith(ABSTRACT)) {
      currentData.setQuery(new GetInstancesOf(groupLink.getTarget()));
      ctx.process(ctx.newCreateTransientLink(Flow.DOWNSTREAM, currentGroup.getId().getParentId(), UNDEFINED, currentGroup.getId().getLastIdFragment(), LANG_MEANING_GROUP)).get();
    }
    else if (conceptIs.isLinkedWith(CONCRETE)) {
      currentData.setQuery(new GetConcepts(groupLink.getTarget()));
    }
    else {
      throw new UnderstandException(groupLink.getTarget() + " has not ABSTRACT nor CONCRETE definition.");
    }
  }

  /**
   * Sets {@link MeaningGroupData}'s {@link Attribute} with current word {@link Concept}, been this an {@link Attribute} and the target of 'groupLink'.
   */
  public static void setGroupConditionWithAttribute(Context ctx, Action action, Primitive currentGroup, MeaningGroupData currentData, Link wordLink, Link groupLink) throws InterruptedException, ExecutionException, UnderstandException {
    Attribute attribute = (Attribute)ctx.process(new GetConcepts(groupLink.getTarget())).get().get(0);
    currentData.setAttribute(attribute);
  }

  /** Sets meaning group lang entity to 'subject', 'action', etc. Needs group 'main' element already set. */
  private static void setGroupLangEntity(Context ctx, Action action, Primitive currentGroup, MeaningGroupData currentData, Link wordLink, Link groupLink) throws InterruptedException, ExecutionException, UnderstandException {
    Link mainLink  = null;
    Link nexusLink = null; 
    for (Link link: currentGroup.getLinks().values()) {
      if (link.isLinkedWith(MAIN))
        mainLink = link;
      if (link.isLinkedWith(LANG_NEXUS))
        nexusLink = link;
    }
    if (mainLink == null)
      throw new UnderstandException(groupLink.getTarget() + " has not MAIN element set.");

    // Set currentGroup 'be-ser' link ... it has this already ... 
    // ctx.process(ctx.newCreateTransientLink(Flow.DOWNSTREAM, currentGroup.getId().getParentId(), LANG_MEANING_GROUP, currentGroup.getId().getLastIdFragment())).get();
    // ctx.process(ctx.newCreateTransientLink(Flow.NONE, currentGroup.getId().getParentId(), TO_BE_SER, currentGroup.getId().getLastIdFragment(), LANG_MEANING_GROUP)).get();

    List<Concept> meaningColumn = extractMeaningChain(ctx, mainLink.getTarget());
    boolean set = false;
    for (Concept concept: meaningColumn) {
      if (ACTION.equals(concept.getId())) {
        ctx.process(ctx.newCreateTransientLink(Flow.DOWNSTREAM, currentGroup.getId().getParentId(), LANG_ACTION, currentGroup.getId().getLastIdFragment(), LANG_MEANING_GROUP)).get();
        set = true;
        break;
      }
    }
    if (!set && (nexusLink != null)) {
      if (nexusLink.isLinkedWith(TO_HAVE)) {
        ctx.process(ctx.newCreateTransientLink(Flow.DOWNSTREAM, currentGroup.getId().getParentId(), LANG_BELONGING_OBJECT, currentGroup.getId().getLastIdFragment(), LANG_MEANING_GROUP)).get();
        set = true;
      }
      if (nexusLink.isLinkedWith(TO_BE_COMPOUND)) {
        ctx.process(ctx.newCreateTransientLink(Flow.DOWNSTREAM, currentGroup.getId().getParentId(), LANG_COMPOSITION_OBJECT, currentGroup.getId().getLastIdFragment(), LANG_MEANING_GROUP)).get();
        set = true;        
      }
      if (nexusLink.isLinkedWith(TO_DATE)) {
        ctx.process(ctx.newCreateTransientLink(Flow.DOWNSTREAM, currentGroup.getId().getParentId(), LANG_TIME_OBJECT, currentGroup.getId().getLastIdFragment(), LANG_MEANING_GROUP)).get();
        set = true;        
      }
      //ctx.process(ctx.newCreateTransientLink(Flow.DOWNSTREAM, currentGroup.getId().getParentId(), LANG_INDIRECT_OBJECT, currentGroup.getId().getLastIdFragment(), LANG_MEANING_GROUP)).get();
    }
    // We don't have an specific lang entity for this group.
    if (!set)
      ctx.process(ctx.newCreateTransientLink(Flow.DOWNSTREAM, currentGroup.getId().getParentId(), LANG_ENTITY, currentGroup.getId().getLastIdFragment(), LANG_MEANING_GROUP)).get();
  }

  protected static List<Concept> extractMeaningChain(Context ctx, Id id) throws InterruptedException, ExecutionException {
    List<Concept> aux = ctx.process(new GetAbstractionsOf(id)).get();
    List<Concept> meaningChain = new ArrayList<Concept>(aux.size()+1);
    meaningChain.addAll(ctx.process(new GetConcepts(id)).get());
    meaningChain.addAll(aux);
    return meaningChain;
  }
}